module Test
  module Unit
    class TestResult
      attr_reader :omissions

      alias_method(:initialize_without_omissions, :initialize)
      def initialize
        initialize_without_omissions
        @omissions = []
      end

      def add_omission(omission)
        @omissions << omission
        notify_listeners(FAULT, omission)
        notify_listeners(CHANGED, self)
      end

      def omission_count
        @omissions.size
      end

      alias_method(:to_s_without_omissions, :to_s)
      def to_s
        to_s_without_omissions + ", #{omission_count} omissions"
      end
    end

    class Omission
      include Util::BacktraceFilter
      attr_reader :test_name, :location, :message
      
      SINGLE_CHARACTER = 'O'

      # Creates a new Omission with the given location and
      # message.
      def initialize(test_name, location, message)
        @test_name = test_name
        @location = location
        @message = message
      end
      
      # Returns a single character representation of a omission.
      def single_character_display
        SINGLE_CHARACTER
      end

      # Returns a brief version of the error description.
      def short_display
        "#@test_name: #{@message.split("\n")[0]}"
      end

      # Returns a verbose version of the error description.
      def long_display
        backtrace = filter_backtrace(location).join("\n")
        "Omission:\n#{@test_name}\n#{@message}\n#{backtrace}"
      end

      # Overridden to return long_display.
      def to_s
        long_display
      end
    end

    class OmittedError < StandardError
    end

    module AssertionsWithOmit
      def omit(message="Omitted", &block)
        if block_given?
          begin
            yield
          rescue Exception
            raise OmittedError, message, $!.backtrace
          end
          flunk("Omission block should not be passed: #{message}")
        else
          raise OmittedError.new(message)
        end
      end
    end

    class TestCase
      include AssertionsWithOmit

      alias_method(:add_error_without_omission, :add_error)
      def add_error(exception)
        if exception.is_a?(OmittedError)
          omission = Omission.new(name, exception.backtrace, exception.message)
          @_result.add_omission(omission)
        else
          add_error_without_omission(exception)
        end
      end
    end
  end
end
